﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;

namespace Microsoft.Windows.Controls
{
    public class Wizard : ItemsControl
    {
        #region Properties

        public static readonly DependencyProperty BackButtonContentProperty = DependencyProperty.Register("BackButtonContent", typeof(object), typeof(Wizard), new UIPropertyMetadata("< Back"));
        public object BackButtonContent
        {
            get { return (object)GetValue(BackButtonContentProperty); }
            set { SetValue(BackButtonContentProperty, value); }
        }

        public static readonly DependencyProperty BackButtonVisibilityProperty = DependencyProperty.Register("BackButtonVisibility", typeof(Visibility), typeof(Wizard), new UIPropertyMetadata(Visibility.Visible));
        public Visibility BackButtonVisibility
        {
            get { return (Visibility)GetValue(BackButtonVisibilityProperty); }
            set { SetValue(BackButtonVisibilityProperty, value); }
        }

        public static readonly DependencyProperty CanCancelProperty = DependencyProperty.Register("CanCancel", typeof(bool), typeof(Wizard), new UIPropertyMetadata(true));
        public bool CanCancel
        {
            get { return (bool)GetValue(CanCancelProperty); }
            set { SetValue(CanCancelProperty, value); }
        }

        public static readonly DependencyProperty CancelButtonClosesWindowProperty = DependencyProperty.Register("CancelButtonClosesWindow", typeof(bool), typeof(Wizard), new UIPropertyMetadata(true));
        public bool CancelButtonClosesWindow
        {
            get { return (bool)GetValue(CancelButtonClosesWindowProperty); }
            set { SetValue(CancelButtonClosesWindowProperty, value); }
        }

        public static readonly DependencyProperty CancelButtonContentProperty = DependencyProperty.Register("CancelButtonContent", typeof(object), typeof(Wizard), new UIPropertyMetadata("Cancel"));
        public object CancelButtonContent
        {
            get { return (object)GetValue(CancelButtonContentProperty); }
            set { SetValue(CancelButtonContentProperty, value); }
        }

        public static readonly DependencyProperty CancelButtonVisibilityProperty = DependencyProperty.Register("CancelButtonVisibility", typeof(Visibility), typeof(Wizard), new UIPropertyMetadata(Visibility.Visible));
        public Visibility CancelButtonVisibility
        {
            get { return (Visibility)GetValue(CancelButtonVisibilityProperty); }
            set { SetValue(CancelButtonVisibilityProperty, value); }
        }

        public static readonly DependencyProperty CanFinishProperty = DependencyProperty.Register("CanFinish", typeof(bool), typeof(Wizard), new UIPropertyMetadata(false));
        public bool CanFinish
        {
            get { return (bool)GetValue(CanFinishProperty); }
            set { SetValue(CanFinishProperty, value); }
        }

        public static readonly DependencyProperty CanHelpProperty = DependencyProperty.Register("CanHelp", typeof(bool), typeof(Wizard), new UIPropertyMetadata(true));
        public bool CanHelp
        {
            get { return (bool)GetValue(CanHelpProperty); }
            set { SetValue(CanHelpProperty, value); }
        }

        public static readonly DependencyProperty CanSelectNextPageProperty = DependencyProperty.Register("CanSelectNextPage", typeof(bool), typeof(Wizard), new UIPropertyMetadata(true));
        public bool CanSelectNextPage
        {
            get { return (bool)GetValue(CanSelectNextPageProperty); }
            set { SetValue(CanSelectNextPageProperty, value); }
        }

        public static readonly DependencyProperty CanSelectPreviousPageProperty = DependencyProperty.Register("CanSelectPreviousPage", typeof(bool), typeof(Wizard), new UIPropertyMetadata(true));
        public bool CanSelectPreviousPage
        {
            get { return (bool)GetValue(CanSelectPreviousPageProperty); }
            set { SetValue(CanSelectPreviousPageProperty, value); }
        }

        #region CurrentPage

        public static readonly DependencyProperty CurrentPageProperty = DependencyProperty.Register("CurrentPage", typeof(WizardPage), typeof(Wizard), new UIPropertyMetadata(null, OnCurrentPageChanged));
        public WizardPage CurrentPage
        {
            get { return (WizardPage)GetValue(CurrentPageProperty); }
            set { SetValue(CurrentPageProperty, value); }
        }

        private static void OnCurrentPageChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Wizard wizard = o as Wizard;
            if (wizard != null)
                wizard.OnCurrentPageChanged((WizardPage)e.OldValue, (WizardPage)e.NewValue);
        }

        protected virtual void OnCurrentPageChanged(WizardPage oldValue, WizardPage newValue)
        {
            RaiseRoutedEvent(Wizard.PageChangedEvent);
        }

        #endregion //CurrentPage

        public static readonly DependencyProperty ExteriorPanelMinWidthProperty = DependencyProperty.Register("ExteriorPanelMinWidth", typeof(double), typeof(Wizard), new UIPropertyMetadata(165.0));
        public double ExteriorPanelMinWidth
        {
            get { return (double)GetValue(ExteriorPanelMinWidthProperty); }
            set { SetValue(ExteriorPanelMinWidthProperty, value); }
        }


        public static readonly DependencyProperty FinishButtonClosesWindowProperty = DependencyProperty.Register("FinishButtonClosesWindow", typeof(bool), typeof(Wizard), new UIPropertyMetadata(true));
        public bool FinishButtonClosesWindow
        {
            get { return (bool)GetValue(FinishButtonClosesWindowProperty); }
            set { SetValue(FinishButtonClosesWindowProperty, value); }
        }

        public static readonly DependencyProperty FinishButtonContentProperty = DependencyProperty.Register("FinishButtonContent", typeof(object), typeof(Wizard), new UIPropertyMetadata("Finish"));
        public object FinishButtonContent
        {
            get { return (object)GetValue(FinishButtonContentProperty); }
            set { SetValue(FinishButtonContentProperty, value); }
        }

        public static readonly DependencyProperty FinishButtonVisibilityProperty = DependencyProperty.Register("FinishButtonVisibility", typeof(Visibility), typeof(Wizard), new UIPropertyMetadata(Visibility.Visible));
        public Visibility FinishButtonVisibility
        {
            get { return (Visibility)GetValue(FinishButtonVisibilityProperty); }
            set { SetValue(FinishButtonVisibilityProperty, value); }
        }

        public static readonly DependencyProperty HelpButtonContentProperty = DependencyProperty.Register("HelpButtonContent", typeof(object), typeof(Wizard), new UIPropertyMetadata("Help"));
        public object HelpButtonContent
        {
            get { return (object)GetValue(HelpButtonContentProperty); }
            set { SetValue(HelpButtonContentProperty, value); }
        }

        public static readonly DependencyProperty HelpButtonVisibilityProperty = DependencyProperty.Register("HelpButtonVisibility", typeof(Visibility), typeof(Wizard), new UIPropertyMetadata(Visibility.Visible));
        public Visibility HelpButtonVisibility
        {
            get { return (Visibility)GetValue(HelpButtonVisibilityProperty); }
            set { SetValue(HelpButtonVisibilityProperty, value); }
        }

        public static readonly DependencyProperty NextButtonContentProperty = DependencyProperty.Register("NextButtonContent", typeof(object), typeof(Wizard), new UIPropertyMetadata("Next >"));
        public object NextButtonContent
        {
            get { return (object)GetValue(NextButtonContentProperty); }
            set { SetValue(NextButtonContentProperty, value); }
        }

        public static readonly DependencyProperty NextButtonVisibilityProperty = DependencyProperty.Register("NextButtonVisibility", typeof(Visibility), typeof(Wizard), new UIPropertyMetadata(Visibility.Visible));
        public Visibility NextButtonVisibility
        {
            get { return (Visibility)GetValue(NextButtonVisibilityProperty); }
            set { SetValue(NextButtonVisibilityProperty, value); }
        }

        #endregion //Properties

        #region Constructors

        static Wizard()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Wizard), new FrameworkPropertyMetadata(typeof(Wizard)));
        }

        public Wizard()
        {
            CommandBindings.Add(new CommandBinding(WizardCommands.Cancel, ExecuteCancelWizard, CanExecuteCancelWizard));
            CommandBindings.Add(new CommandBinding(WizardCommands.Finish, ExecuteFinishWizard, CanExecuteFinishWizard));
            CommandBindings.Add(new CommandBinding(WizardCommands.Help, ExecuteRequestHelp, CanExecuteRequestHelp));
            CommandBindings.Add(new CommandBinding(WizardCommands.NextPage, ExecuteSelectNextPage, CanExecuteSelectNextPage));
            CommandBindings.Add(new CommandBinding(WizardCommands.PreviousPage, ExecuteSelectPreviousPage, CanExecuteSelectPreviousPage));
        }

        #endregion //Constructors

        #region Base Class Overrides

        protected override DependencyObject GetContainerForItemOverride()
        {
            return new WizardPage();
        }

        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return (item is WizardPage);
        }

        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);

            if (Items.Count > 0 && CurrentPage == null)
                CurrentPage = Items[0] as WizardPage;
        }

        #endregion //Base Class Overrides

        #region Commands

        private void ExecuteCancelWizard(object sender, ExecutedRoutedEventArgs e)
        {
            RaiseRoutedEvent(Wizard.CancelEvent);

            if (CancelButtonClosesWindow)
                CloseParentWindow(false);
        }

        private void CanExecuteCancelWizard(object sender, CanExecuteRoutedEventArgs e)
        {
            if (CurrentPage != null)
            {
                if (CurrentPage.CanCancel.HasValue)
                    e.CanExecute = CurrentPage.CanCancel.Value;
                else
                    e.CanExecute = CanCancel;
            }
        }

        private void ExecuteFinishWizard(object sender, ExecutedRoutedEventArgs e)
        {
            RaiseRoutedEvent(Wizard.FinishEvent);

            if (FinishButtonClosesWindow)
                CloseParentWindow(true);
        }

        private void CanExecuteFinishWizard(object sender, CanExecuteRoutedEventArgs e)
        {
            if (CurrentPage != null)
            {
                if (CurrentPage.CanFinish.HasValue)
                    e.CanExecute = CurrentPage.CanFinish.Value;
                else
                    e.CanExecute = CanFinish;
            }
        }

        private void ExecuteRequestHelp(object sender, ExecutedRoutedEventArgs e)
        {
            RaiseRoutedEvent(Wizard.HelpEvent);
        }

        private void CanExecuteRequestHelp(object sender, CanExecuteRoutedEventArgs e)
        {
            if (CurrentPage != null)
            {
                if (CurrentPage.CanHelp.HasValue)
                    e.CanExecute = CurrentPage.CanHelp.Value;
                else
                    e.CanExecute = CanHelp;
            }
        }

        private void ExecuteSelectNextPage(object sender, ExecutedRoutedEventArgs e)
        {
            WizardPage nextPage = null;

            if (CurrentPage != null)
            {
                //check next page
                if (CurrentPage.NextPage != null)
                    nextPage = CurrentPage.NextPage;
                else
                {
                    //no next page defined use index
                    var currentIndex = Items.IndexOf(CurrentPage);
                    var nextPageIndex = currentIndex + 1;
                    if (nextPageIndex < Items.Count)
                        nextPage = Items[nextPageIndex] as WizardPage;
                }
                
                if (this.CurrentPage.NextCommand != null)
                {
                    this.CurrentPage.NextCommand.Execute(this.CurrentPage);
                }
            }

            CurrentPage = nextPage;
        }

        private void CanExecuteSelectNextPage(object sender, CanExecuteRoutedEventArgs e)
        {
            if (CurrentPage != null)
            {
                if (CurrentPage.CanSelectNextPage.HasValue) //check to see if page has overriden default behavior
                {
                    if (CurrentPage.CanSelectNextPage.Value)
                        e.CanExecute = NextPageExists();
                }
                else if (CurrentPage.NextCommand != null)
                {
                    e.CanExecute = this.CurrentPage.NextCommand.CanExecute(this.CurrentPage);
                }
                else if (CanSelectNextPage)
                    e.CanExecute = NextPageExists();
            }
        }

        private void ExecuteSelectPreviousPage(object sender, ExecutedRoutedEventArgs e)
        {
            WizardPage previousPage = null;

            if (CurrentPage != null)
            {
                //check previous page
                if (CurrentPage.PreviousPage != null)
                    previousPage = CurrentPage.PreviousPage;
                else
                {
                    //no previous page defined so use index
                    var currentIndex = Items.IndexOf(CurrentPage);
                    var previousPageIndex = currentIndex - 1;
                    if (previousPageIndex >= 0 && previousPageIndex < Items.Count)
                        previousPage = Items[previousPageIndex] as WizardPage;
                }

                if (this.CurrentPage.PreviousCommand != null)
                {
                    this.CurrentPage.PreviousCommand.Execute(this.CurrentPage);
                }
            }

            CurrentPage = previousPage;
        }

        private void CanExecuteSelectPreviousPage(object sender, CanExecuteRoutedEventArgs e)
        {
            if (CurrentPage != null)
            {
                if (CurrentPage.CanSelectPreviousPage.HasValue) //check to see if page has overriden default behavior
                {
                    if (CurrentPage.CanSelectPreviousPage.Value)
                        e.CanExecute = PreviousPageExists();
                }
                else if (CurrentPage.PreviousCommand != null)
                {
                    e.CanExecute = this.CurrentPage.PreviousCommand.CanExecute(this.CurrentPage);
                }
                else if (CanSelectPreviousPage)
                    e.CanExecute = PreviousPageExists();
            }
        }

        #endregion //Commands

        #region Events

        public static readonly RoutedEvent CancelEvent = EventManager.RegisterRoutedEvent("Cancel", RoutingStrategy.Bubble, typeof(EventHandler), typeof(Wizard));
        public event RoutedEventHandler Cancel
        {
            add { AddHandler(CancelEvent, value); }
            remove { RemoveHandler(CancelEvent, value); }
        }

        public static readonly RoutedEvent PageChangedEvent = EventManager.RegisterRoutedEvent("PageChanged", RoutingStrategy.Bubble, typeof(EventHandler), typeof(Wizard));
        public event RoutedEventHandler PageChanged
        {
            add { AddHandler(PageChangedEvent, value); }
            remove { RemoveHandler(PageChangedEvent, value); }
        }

        public static readonly RoutedEvent FinishEvent = EventManager.RegisterRoutedEvent("Finish", RoutingStrategy.Bubble, typeof(EventHandler), typeof(Wizard));
        public event RoutedEventHandler Finish
        {
            add { AddHandler(FinishEvent, value); }
            remove { RemoveHandler(FinishEvent, value); }
        }

        public static readonly RoutedEvent HelpEvent = EventManager.RegisterRoutedEvent("Help", RoutingStrategy.Bubble, typeof(EventHandler), typeof(Wizard));
        public event RoutedEventHandler Help
        {
            add { AddHandler(HelpEvent, value); }
            remove { RemoveHandler(HelpEvent, value); }
        }

        #endregion //Events

        #region Methods

        private void CloseParentWindow(bool dialogResult)
        {
            Window window = Window.GetWindow(this);
            if (window != null)
            {
                //we can only set the DialogResult if the window was opened as modal with the ShowDialog() method. Otherwise an exception would occur
                if (ComponentDispatcher.IsThreadModal)
                    window.DialogResult = dialogResult;

                window.Close();
            }
        }

        private bool NextPageExists()
        {
            bool exists = false;

            if (CurrentPage.NextPage != null) //check to see if a next page has been specified
                exists = true;
            else
            {
                //lets use an index to find the next page
                var currentIndex = Items.IndexOf(CurrentPage);
                var nextPageIndex = currentIndex + 1;
                if (nextPageIndex < Items.Count)
                    exists = true;
            }

            return exists;
        }

        private bool PreviousPageExists()
        {
            bool exists = false;

            if (CurrentPage.PreviousPage != null) //check to see if a previous page has been specified
                exists = true;
            else
            {
                //lets use an index to find the next page
                var currentIndex = Items.IndexOf(CurrentPage);
                var previousPageIndex = currentIndex - 1;
                if (previousPageIndex >= 0 && previousPageIndex < Items.Count)
                    exists = true;
            }

            return exists;
        }

        private void RaiseRoutedEvent(RoutedEvent routedEvent)
        {
            RoutedEventArgs newEventArgs = new RoutedEventArgs(routedEvent, this);
            base.RaiseEvent(newEventArgs);
        }

        #endregion //Methods
    }
}